package com.wondersgroup.util;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA非对称加密工具类
 * 
 * @author liujianzhuo
 *
 * 说明:
 *    原因： 前端 jsencrypt 无法使用 公钥进行解密，只能使用公钥加密 私钥解密
 *    措施:  工具类同时生成 两套秘钥对

 *    1.前端发送数据给后端:  使用第一套秘钥
 *
 *        说明:  前端获取公钥 把前端前端需要加密的数据进行加密,后端使用秘钥进行解密
 *
 *    2.后端返回数据给前端:  使用第二套秘钥
 *
 *        说明: 后端使用将需要加密的数据进行加密, 前端获取私钥对数据进行解密
 *
 *
 */
public class RsaUtil {

  /** 指定加密算法为DESede */
  private static String ALGORITHM = "RSA/ECB/PKCS1Padding";
  /** 指定key的大小 */
  private static int KEYSIZE = 1024;
  /** RSA最大加密明文大小 */
  private static final int MAX_ENCRYPT_BLOCK = 117;
  /** RSA最大解密密文大小 */
  private static final int MAX_DECRYPT_BLOCK = 128;
  /** 公钥模量 */
  public static String publicModulus = null;
  /** 公钥指数 */
  public static String publicExponent = null;
  /** 私钥模量 */
  public static String privateModulus = null;
  /** 私钥指数 */
  public static String privateExponent = null;
  /**生成 key*/
  private static KeyFactory keyFactory = null;

  /** 第一套 秘钥对 */
  private static final String PUBLIC_KEY = "RSAPublicKey";
  private static final String PRIVATE_KEY = "RSAPrivateKey";

  /** 第二套 秘钥对 */
  private static final String PUBLIC_KEY_JS = "RSAPublicKeyJs";
  private static final String PRIVATE_KEY_JS = "RSAPrivateKeyJs";

  private static Map<String, Key> keyMap = new HashMap<>(4);

  //根据算法获取公钥对象
  static {
    try {
      keyFactory = KeyFactory.getInstance("RSA");
    } catch (NoSuchAlgorithmException ex) {
      System.out.println(ex.getMessage());
    }
  }
  // 生成秘钥对
  public RsaUtil() {
    try {
      generateKeyPairString(KEYSIZE);
      generateKeyPairStringJs(KEYSIZE);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public RsaUtil(int keySize) {
    try {
      generateKeyPairString(keySize);
      generateKeyPairStringJs(keySize);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 生成密钥对字符串
   */
  private void generateKeyPairString(int keySize) throws Exception {
    /** RSA算法要求有一个可信任的随机数源 */
    SecureRandom sr = new SecureRandom();
    /** 为RSA算法创建一个KeyPairGenerator对象 */
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
    kpg.initialize(keySize, sr);
    /** 生成密匙对 */
    KeyPair kp = kpg.generateKeyPair();
    /** 得到公钥 */
    Key publicKey = kp.getPublic();
    /** 得到私钥 */
    Key privateKey = kp.getPrivate();
    /** 将生成的密钥写入内存Map */
    // 公钥
    keyMap.put(PUBLIC_KEY, kp.getPublic());
    // 私钥
    keyMap.put(PRIVATE_KEY, kp.getPrivate());


    String algorithm = publicKey.getAlgorithm();
    KeyFactory keyFact = KeyFactory.getInstance(algorithm);
    BigInteger prime = null;
    BigInteger exponent = null;

    RSAPublicKeySpec keySpec = (RSAPublicKeySpec) keyFact.getKeySpec(publicKey, RSAPublicKeySpec.class);
    prime = keySpec.getModulus();
    exponent = keySpec.getPublicExponent();
    RsaUtil.publicModulus = HexUtil.bytes2Hex(prime.toByteArray());
    RsaUtil.publicExponent = HexUtil.bytes2Hex(exponent.toByteArray());

    RSAPrivateCrtKeySpec privateKeySpec = (RSAPrivateCrtKeySpec) keyFact.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
    BigInteger privateModulus = privateKeySpec.getModulus();
    BigInteger privateExponent = privateKeySpec.getPrivateExponent();
    RsaUtil.privateModulus = HexUtil.bytes2Hex(privateModulus.toByteArray());
    RsaUtil.privateExponent = HexUtil.bytes2Hex(privateExponent.toByteArray());
  }
  /**
   * 生成密钥 对字符串  前端使用
   */
  private void generateKeyPairStringJs(int keySize) throws Exception {
    /** RSA算法要求有一个可信任的随机数源 */
    SecureRandom sr = new SecureRandom();
    /** 为RSA算法创建一个KeyPairGenerator对象 */
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
    kpg.initialize(keySize, sr);
    /** 生成密匙对 */
    KeyPair kp = kpg.generateKeyPair();
    /** 得到公钥 */
    Key publicKey = kp.getPublic();
    /** 得到私钥 */
    Key privateKey = kp.getPrivate();
    /** 将生成的密钥写入内存Map */
    // 公钥
    keyMap.put(PUBLIC_KEY_JS, kp.getPublic());
    // 私钥
    keyMap.put(PRIVATE_KEY_JS, kp.getPrivate());


    String algorithm = publicKey.getAlgorithm();
    KeyFactory keyFact = KeyFactory.getInstance(algorithm);
    BigInteger prime = null;
    BigInteger exponent = null;

    RSAPublicKeySpec keySpec = (RSAPublicKeySpec) keyFact.getKeySpec(publicKey, RSAPublicKeySpec.class);
    prime = keySpec.getModulus();
    exponent = keySpec.getPublicExponent();
    RsaUtil.publicModulus = HexUtil.bytes2Hex(prime.toByteArray());
    RsaUtil.publicExponent = HexUtil.bytes2Hex(exponent.toByteArray());

    RSAPrivateCrtKeySpec privateKeySpec = (RSAPrivateCrtKeySpec) keyFact.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
    BigInteger privateModulus = privateKeySpec.getModulus();
    BigInteger privateExponent = privateKeySpec.getPrivateExponent();
    RsaUtil.privateModulus = HexUtil.bytes2Hex(privateModulus.toByteArray());
    RsaUtil.privateExponent = HexUtil.bytes2Hex(privateExponent.toByteArray());
  }

  /**
   * 取得Base64加密后的公钥
   *
   * @param  type 1:第一套   2: 第二套
   * @return
   * @throws Exception
   */
  public static String getPublicKeyByBase64(int type) throws Exception {
    Key key = null;
    if(type == 1){
      key = keyMap.get(PUBLIC_KEY);
    }else{
      key = keyMap.get(PUBLIC_KEY_JS);
    }
    return Base64.encodeBase64String(key.getEncoded());
  }

  /**
   * 取得Base64加密后的私钥
   *
   * @param  type 1:第一套   2: 第二套
   * @return
   * @throws Exception
   */
  public static String getPrivateKeyByBase64(int type) throws Exception {
    Key key = null;
    if(type == 1){
      key = keyMap.get(PRIVATE_KEY);
    }else{
      key = keyMap.get(PRIVATE_KEY_JS);
    }
    return Base64.encodeBase64String(key.getEncoded());
  }

  /**
   * 取得公钥
   *
   * type  1: 第一套   2:第二套
   *
   * @param
   * @throws Exception
   */
  public static Key getPublicKey(int type) throws Exception {
    if(type == 1){
      return keyMap.get(PUBLIC_KEY);
    }else{
      return keyMap.get(PUBLIC_KEY_JS);
    }
  }

  /**
   * 取得私钥
   * type 1： 第一套   2： 第二套
   * @return
   * @throws Exception
   */
  public static Key getPrivateKey(int type) throws Exception {
    if(type == 1){
      return keyMap.get(PRIVATE_KEY);
    }else{
      return keyMap.get(PRIVATE_KEY_JS);
    }
  }

  /**
   * 根据给定的16进制系数和专用指数字符串构造一个RSA专用的公钥对象。
   *
   * @param hexModulus 系数。
   * @param hexPublicExponent 专用指数。
   * @return RSA专用公钥对象。
   */
  public static RSAPublicKey getRSAPublicKey(String hexModulus, String hexPublicExponent) {
    if (isBlank(hexModulus) || isBlank(hexPublicExponent)) {
      System.out.println("hexModulus and hexPublicExponent cannot be empty. return null(RSAPublicKey).");
      return null;
    }
    byte[] modulus = null;
    byte[] publicExponent = null;
    try {
      modulus = HexUtil.hex2Bytes(hexModulus);
      publicExponent = HexUtil.hex2Bytes(hexPublicExponent);
    } catch (Exception ex) {
      System.out.println("hexModulus or hexPublicExponent value is invalid. return null(RSAPublicKey).");
      ex.printStackTrace();
    }
    if (modulus != null && publicExponent != null) {
      return generateRSAPublicKey(modulus, publicExponent);
    }
    return null;
  }

  /**
   * 根据给定的系数和专用指数构造一个RSA专用的公钥对象。
   *
   * @param modulus 系数。
   * @param publicExponent 专用指数。
   * @return RSA专用公钥对象。
   */
  public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) {
    RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
    try {
      return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
    } catch (InvalidKeySpecException ex) {
      System.out.println("RSAPublicKeySpec is unavailable.");
      ex.printStackTrace();
    } catch (NullPointerException ex) {
      System.out.println("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance.");
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * 根据给定的16进制系数和专用指数字符串构造一个RSA专用的私钥对象。
   *
   * @param hexModulus 系数。
   * @param hexPrivateExponent 专用指数。
   * @return RSA专用私钥对象。
   */
  public static RSAPrivateKey getRSAPrivateKey(String hexModulus, String hexPrivateExponent) {
    if (isBlank(hexModulus) || isBlank(hexPrivateExponent)) {
      System.out.println(
        "hexModulus and hexPrivateExponent cannot be empty. RSAPrivateKey value is null to return.");
      return null;
    }
    byte[] modulus = null;
    byte[] privateExponent = null;
    try {
      modulus = HexUtil.hex2Bytes(hexModulus);
      privateExponent = HexUtil.hex2Bytes(hexPrivateExponent);
    } catch (Exception ex) {
      System.out.println("hexModulus or hexPrivateExponent value is invalid. return null(RSAPrivateKey).");
      ex.printStackTrace();
    }
    if (modulus != null && privateExponent != null) {
      return generateRSAPrivateKey(modulus, privateExponent);
    }
    return null;
  }

  /**
   * 根据给定的系数和专用指数构造一个RSA专用的私钥对象。
   *
   * @param modulus 系数。
   * @param privateExponent 专用指数。
   * @return RSA专用私钥对象。
   */
  public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) {
    RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus),
      new BigInteger(privateExponent));
    try {
      return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);
    } catch (InvalidKeySpecException ex) {
      System.out.println("RSAPrivateKeySpec is unavailable.");
      ex.printStackTrace();
    } catch (NullPointerException ex) {
      System.out.println("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance.");
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * 使用给定的公钥加密给定的字符串。
   *
   * @param key 给定的公钥。
   * @param plaintext 字符串。
   * @return 给定字符串的密文。
   */
  public static String encryptString(Key key, String plaintext) {
    if (key == null || plaintext == null) {
      return null;
    }
    byte[] data = plaintext.getBytes();
    try {
      byte[] enData = encrypt(key, data);
      return new String(Base64.encodeBase64String(enData));
      // return new String(HexUtil.bytes2Hex(enData));
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * 使用指定的公钥加密数据。
   *
   * @param key 给定的公钥。
   * @param data 要加密的数据。
   * @return 加密后的数据。
   */
  public static byte[] encrypt(Key key, byte[] data) throws Exception {
    Cipher ci = Cipher.getInstance(ALGORITHM);
    ci.init(Cipher.ENCRYPT_MODE, key);
    int inputLen = data.length;
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    int offSet = 0;
    byte[] cache;
    int i = 0;
    // 对数据分段加密
    while (inputLen - offSet > 0) {
      if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
        cache = ci.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
      } else {
        cache = ci.doFinal(data, offSet, inputLen - offSet);
      }
      out.write(cache, 0, cache.length);
      i++;
      offSet = i * MAX_ENCRYPT_BLOCK;
    }
    byte[] encryptedData = out.toByteArray();
    out.close();
    return encryptedData;
  }

  /**
   * 使用给定的公钥解密给定的字符串。
   *
   * @param key 给定的公钥
   * @param encrypttext 密文
   * @return 原文字符串。
   */
  public static String decryptString(Key key, String encrypttext) {
    if (key == null || isBlank(encrypttext)) {
      return null;
    }
    try {
      byte[] enData = Base64.decodeBase64(encrypttext);
      // byte[] enData = HexUtil.hex2Bytes(encrypttext);
      byte[] data = decrypt(key, enData);
      return new String(data);
    } catch (Exception ex) {
      ex.printStackTrace();
      System.out.println(
        String.format("\"%s\" Decryption failed. Cause: %s", encrypttext, ex.getCause().getMessage()));
    }
    return null;
  }

  /**
   * 使用指定的公钥解密数据。
   *
   * @param key 指定的公钥
   * @param data 要解密的数据
   * @return 原数据
   * @throws Exception
   */
  public static byte[] decrypt(Key key, byte[] data) throws Exception {
    Cipher ci = Cipher.getInstance(ALGORITHM);
    ci.init(Cipher.DECRYPT_MODE, key);
    // return ci.doFinal(data);
    int inputLen = data.length;
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    int offSet = 0;
    byte[] cache;
    int i = 0;
    // 对数据分段解密
    while (inputLen - offSet > 0) {
      if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
        cache = ci.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
      } else {
        cache = ci.doFinal(data, offSet, inputLen - offSet);
      }
      out.write(cache, 0, cache.length);
      i++;
      offSet = i * MAX_DECRYPT_BLOCK;
    }
    byte[] decryptedData = out.toByteArray();
    out.close();
    return decryptedData;
  }

  /**
   * 判断非空字符串
   *
   * @param cs 待判断的CharSequence序列
   * @return 是否非空
   */
  private static boolean isBlank(final CharSequence cs) {
    int strLen;
    if (cs == null || (strLen = cs.length()) == 0) {
      return true;
    }
    for (int i = 0; i < strLen; i++) {
      if (Character.isWhitespace(cs.charAt(i)) == false) {
        return false;
      }
    }
    return true;
  }

  /**
   *
   * 使用 第二套  公钥加密
   * @param source
   * @throws Exception
   */
  public static String getEncData(String source) throws  Exception{
    return  encryptString(getPublicKey(2), source);
  }

  /**
   * 使用 第一套  私钥解密
   * @param source
   * @return
   */
  public static String getDecData(String source) throws Exception{
    return  decryptString(getPrivateKey(1), source);
  }


  public static void main(String[] args) throws Exception{
    new RsaUtil();
    System.out.println(keyMap.size());
    System.out.println(keyMap.toString());
    String content = "123";
    System.out.println("加密前："+ content);
    System.out.println("--------公钥加密   私钥解密------");
    System.out.println("1.加密后："+RsaUtil.encryptString(getPublicKey(1), content));
    System.out.println("1.解密后："+RsaUtil.decryptString(getPrivateKey(1), RsaUtil.encryptString(getPublicKey(1), content)));

    System.out.println("2.加密后："+RsaUtil.encryptString(getPublicKey(2), content));
    System.out.println("2.解密后："+RsaUtil.decryptString(getPrivateKey(2), RsaUtil.encryptString(getPublicKey(2), content)));


    System.out.println("--------私钥加密   公钥解密------");
    System.out.println("1.加密后："+RsaUtil.encryptString(getPrivateKey(1), content));
    System.out.println("1.解密后："+RsaUtil.decryptString(getPublicKey(1), RsaUtil.encryptString(getPrivateKey(1), content)));

    System.out.println("2.加密后："+RsaUtil.encryptString(getPrivateKey(2), content));
    System.out.println("2.解密后："+RsaUtil.decryptString(getPublicKey(2), RsaUtil.encryptString(getPrivateKey(2), content)));



    /*
		String content = "123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！123hello你好！";
        System.out.println(content.getBytes().length);
		System.out.println("获得公钥：" + RsaUtil.getPublicKeyByBase64());
		System.out.println("获得私钥：" + RsaUtil.getPrivateKeyByBase64());
		// 公钥加密，私钥解密
		String encript = RsaUtil.encryptString(RsaUtil.getPublicKey(), content);
		System.out.println("加密后数据：" + encript);
		String newSource = RsaUtil.decryptString(RsaUtil.getPrivateKey(), encript);
		System.out.println("解密后数据：" + newSource);*/

    // 私钥加密，公钥解密
//		String priKeyStr = RSAUtil.encryptString(RSAUtil.getPrivateKey(), source);
//		System.out.println("加密后数据：" + priKeyStr);
//		String oldSource = RSAUtil.decryptString(RSAUtil.getPublicKey(), priKeyStr);
//		System.out.println("解密后数据：" + oldSource);
  }
}